﻿using BeetleX.Clients;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

#nullable disable
namespace BeetleX.XRPC.Clients
{
    internal class DelegateHandler
    {
        private PropertyInfo mResultProperty;
        private XRPCClient xRPCClient;

        public DelegateHandler(Type type)
        {
            this.Type = type;
            this.Method = type.GetMethod("Invoke");
        }

        public static string GetDelegateName(Type type)
        {
            string delegateName = type.Name;
            if (type.IsGenericType)
            {
                string str = delegateName + "[";
                Type[] genericArguments = type.GetGenericArguments();
                for (int index = 0; index < genericArguments.Length; ++index)
                {
                    str += DelegateHandler.GetDelegateName(genericArguments[index]);
                    if (index > 0)
                        str += ",";
                }
                delegateName = str + "]";
            }
            return delegateName;
        }

        public Type Type { get; set; }

        public string Name { get; set; }

        public bool IsVoid { get; set; }

        public Type TargetReturnType { get; set; }

        public Type[] GetReturnTypes()
        {
            if (this.TargetReturnType == (Type)null)
                return new Type[0];
            return new Type[1] { this.TargetReturnType };
        }

        public Type[] Parameters { get; set; }

        public MethodInfo Method { get; set; }

        public Delegate Delegate { get; set; }

        public Delegate ClientDelegateProxy { get; set; }

        public object GetValue(object result)
        {
            return this.mResultProperty != (PropertyInfo)null ? this.mResultProperty.GetValue(result) : (object)null;
        }

        public void Init()
        {
            this.Name = DelegateHandler.GetDelegateName(this.Type);
            this.Parameters = ((IEnumerable<ParameterInfo>)this.Method.GetParameters()).Select<ParameterInfo, Type>((Func<ParameterInfo, Type>)(a => a.ParameterType)).ToArray<Type>();
            foreach (ParameterInfo parameter in this.Method.GetParameters())
            {
                if (parameter.ParameterType == typeof(object))
                    throw new XRPCException(parameter.Name + " parameters cannot be object type");
            }
            if (this.Method.ReturnType == typeof(void))
            {
                this.IsVoid = true;
            }
            else
            {
                this.mResultProperty = this.Method.ReturnType.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public);
                if (!(this.Method.ReturnType == typeof(Task)) && !(this.Method.ReturnType.BaseType == typeof(Task)))
                    throw new XRPCException("The method return value must be void or task!");
                if (!this.Method.ReturnType.IsGenericType)
                    return;
                this.TargetReturnType = this.Method.ReturnType.GetGenericArguments()[0];
            }
        }

        public void Invoke() => this.OnVoidExecute();

        public void Invoke<T>(T p1) => this.OnVoidExecute((object)p1);

        public void Invoke<T, T1>(T p1, T1 p2) => this.OnVoidExecute((object)p1, (object)p2);

        public void Invoke<T, T1, T2>(T p1, T1 p2, T2 p3)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3);
        }

        public void Invoke<T, T1, T2, T3>(T p1, T1 p2, T2 p3, T3 p4)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4);
        }

        public void Invoke<T, T1, T2, T3, T4>(T p1, T1 p2, T2 p3, T3 p4, T4 p5)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5);
        }

        public void Invoke<T, T1, T2, T3, T4, T5>(T p1, T1 p2, T2 p3, T3 p4, T4 p5, T5 p6)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6);
        }

        public void Invoke<T, T1, T2, T3, T4, T5, T6>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7);
        }

        public void Invoke<T, T1, T2, T3, T4, T5, T6, T7>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8);
        }

        public void Invoke<T, T1, T2, T3, T4, T5, T6, T7, T8>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8, (object)p9);
        }

        public void Invoke<T, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9,
          T9 p10)
        {
            this.OnVoidExecute((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8, (object)p9, (object)p10);
        }

        public async Task TaskInvoke()
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>();
        }

        public async Task TaskInvoke<T>(T p1)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1);
        }

        public async Task TaskInvoke<T, T1>(T p1, T1 p2)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2);
        }

        public async Task TaskInvoke<T, T1, T2>(T p1, T1 p2, T2 p3)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3);
        }

        public async Task TaskInvoke<T, T1, T2, T3>(T p1, T1 p2, T2 p3, T3 p4)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4>(T p1, T1 p2, T2 p3, T3 p4, T4 p5)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4, T5>(T p1, T1 p2, T2 p3, T3 p4, T4 p5, T5 p6)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5, (object)(T5)p6);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4, T5, T6>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5, (object)(T5)p6, (object)(T6)p7);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4, T5, T6, T7>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5, (object)(T5)p6, (object)(T6)p7, (object)(T7)p8);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4, T5, T6, T7, T8>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5, (object)(T5)p6, (object)(T6)p7, (object)(T7)p8, (object)(T8)p9);
        }

        public async Task TaskInvoke<T, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9,
          T9 p10)
        {
            RESULT_NULL resultNull = await this.OnTaskExecute<RESULT_NULL>((object)(T)p1, (object)(T1)p2, (object)(T2)p3, (object)(T3)p4, (object)(T4)p5, (object)(T5)p6, (object)(T6)p7, (object)(T7)p8, (object)(T8)p9, (object)(T9)p10);
        }

        public Task<Result> TaskResultInvoke<Result>() => this.OnTaskExecute<Result>();

        public Task<Result> TaskResultInvoke<Result, T>(T p1)
        {
            return this.OnTaskExecute<Result>((object)p1);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1>(T p1, T1 p2)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2>(T p1, T1 p2, T2 p3)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3>(T p1, T1 p2, T2 p3, T3 p4)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4, T5>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4, T5, T6>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4, T5, T6, T7>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4, T5, T6, T7, T8>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8, (object)p9);
        }

        public Task<Result> TaskResultInvoke<Result, T, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
          T p1,
          T1 p2,
          T2 p3,
          T3 p4,
          T4 p5,
          T5 p6,
          T6 p7,
          T7 p8,
          T8 p9,
          T9 p10)
        {
            return this.OnTaskExecute<Result>((object)p1, (object)p2, (object)p3, (object)p4, (object)p5, (object)p6, (object)p7, (object)p8, (object)p9, (object)p10);
        }

        public MethodInfo GetMethod(string method, int parameters)
        {
            foreach (MethodInfo method1 in this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public))
            {
                if (method1.GetParameters().Length == parameters && method1.Name.IndexOf(method) == 0)
                    return method1;
            }
            throw new XRPCException("Delegate proxy method not support!");
        }

        protected Delegate CreateDelegate()
        {
            List<Type> list = ((IEnumerable<ParameterInfo>)this.Method.GetParameters()).Select<ParameterInfo, Type>((Func<ParameterInfo, Type>)(a => a.ParameterType)).ToList<Type>();
            Delegate @delegate;
            if (this.IsVoid)
            {
                MethodInfo method = this.GetMethod("Invoke", list.Count);
                @delegate = Delegate.CreateDelegate(this.Type, (object)this, list.Count > 0 ? method.MakeGenericMethod(list.ToArray()) : method);
            }
            else if (this.TargetReturnType == (Type)null)
            {
                MethodInfo method = this.GetMethod("TaskInvoke", list.Count);
                @delegate = Delegate.CreateDelegate(this.Type, (object)this, list.Count > 0 ? method.MakeGenericMethod(list.ToArray()) : method);
            }
            else
            {
                MethodInfo method = this.GetMethod("TaskResultInvoke", list.Count);
                list.Insert(0, this.TargetReturnType);
                Type[] array = list.ToArray();
                @delegate = Delegate.CreateDelegate(this.Type, (object)this, method.MakeGenericMethod(array));
            }
            return @delegate;
        }

        protected virtual Task OnVoidExecute(params object[] data)
        {
            return this.xRPCClient.Send(new RPCPacket()
            {
                NeedReply = false,
                Url = "/__delegate/" + this.Name,
                Data = data
            });
        }

        protected virtual async Task<T> OnTaskExecute<T>(params object[] data)
        {
            bool istask = typeof(T) == typeof(RESULT_NULL);
            using RPCPacket request = new RPCPacket();
            request.NeedReply = true;
            request.Url = "/__delegate/" + this.Name;
            request.Data = data;
            Type[] returnTypes = this.GetReturnTypes();
            RPCPacket rpcPacket = await this.xRPCClient.SendWait(request, (AsyncTcpClient)null, returnTypes);
            if (rpcPacket.Status != 200)
                throw new XRPCException((string)rpcPacket.Data[0]);

            if (istask)
                return (T)(object)new RESULT_NULL();
            return (T)rpcPacket.Data[0];
        }

        public void Bind(XRPCClient client)
        {
            this.ClientDelegateProxy = this.CreateDelegate();
            this.xRPCClient = client;
        }
    }
}
